library(tidyverse)
library(lubridate)
library(ggplot2)
library(plotly)
library(grid) #for adjust grid scale
#import data from url or file

# Chiang Mai - http://berkeleyearth.lbl.gov/air-quality/maps/cities/Thailand/Chiang_Mai/Chiang_Mai.txt
# Bangkok - http://berkeleyearth.lbl.gov/air-quality/maps/cities/Thailand/Bangkok/Bangkok.txt

url <- 'http://berkeleyearth.lbl.gov/air-quality/maps/cities/Thailand/Chiang_Mai/Chiang_Mai.txt'
df <- read_tsv(url,skip=10, col_names = FALSE)
Parsed with column specification:
cols(
  X1 = col_double(),
  X2 = col_double(),
  X3 = col_double(),
  X4 = col_double(),
  X5 = col_double(),
  X6 = col_double(),
  X7 = col_double()
)
df
colnames(df) <- c('Year','Month','Day','Hour','PM2_5','X6','X7')
df
df <- df %>%
  select(Year:PM2_5)
df
df <- df %>% 
  mutate(date_time = ISOdate(Year,Month,Day,Hour),
         local_dt = date_time+hours(7),
         local_hr = hour(local_dt))
df

—— Summarized Data (Reserved) ——

df %>% 
  mutate(year = year(local_dt),
         month = month(local_dt),
         date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5))

—— Visualized Data ——

## entire data

df %>% 
  mutate(year = year(local_dt),
         month = month(local_dt),
         date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  ggplot(aes(date, y = avg_pm , color = as.factor(year(date))))+geom_line()+geom_smooth()

by date for each month

df %>% 
  mutate(year = year(local_dt),
         month = month(local_dt),
         date = date(local_dt)) %>%
  group_by(year,month,date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  filter(year == 2019) %>%
  ggplot(aes(date, y = avg_pm , color = as.factor(month)))+geom_line()

By month for each year

df %>% 
  mutate(year = year(local_dt),
         month = month(local_dt),
         date = date(local_dt)) %>%
  group_by(year,month) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  ggplot(aes(month, y = avg_pm , color = as.factor(year)))+ geom_smooth(se=FALSE) +scale_x_discrete(limits =month.abb)  

## create heat map

df %>% 
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = day(date),
         month = month(date),
         year = year(date)) %>%
  ggplot(aes(day, y=month, fill=avg_pm)) + geom_tile()

## add factor to map, make it display by day

df %>% 
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = as.factor(month(date)),
         year = as.factor(year(date))) %>%
  ggplot(aes(day, y=month, fill=avg_pm)) + geom_tile()

## add border and set a color

df %>% 
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = as.factor(month(date)),
         year = as.factor(year(date))) %>%
  ggplot(aes(day, y=month, fill=avg_pm)) +
  geom_tile(color="white") +
  scale_fill_gradient(low = "green" , high = 'red')

adjust cell scale

df %>% 
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = as.factor(month(date)),
         year = as.factor(year(date))) %>%
  ggplot(aes(day, y=month, fill=avg_pm)) +
  geom_tile(color="white") +
  scale_fill_gradient(low = "green" , high = 'red') +
  coord_equal()

set interval factor for avg pm 2.5

df %>% 
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = as.factor(month(date)),
         year = as.factor(year(date))) %>%
  mutate(pm_lv = cut(avg_pm, ## create new column to classify level of smog
         breaks = c(0,12,35.4,55.4,150.4,250.4,350.4),
         labels = c('good','moderate','unheathy for sensitive groups'
                    ,'unheathy','very unheathy','hazardous'))) %>%
  ggplot(aes(day, y=month, fill=pm_lv)) + ## replace avg_pm with pm_level
  geom_tile(color="white") +
  scale_fill_manual(values = c('green','yellow','orange','red','magenta', 'violet'))+
  coord_equal()

separate by year

df %>% 
  filter(year(local_dt) > 2015) %>%
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = as.factor(month(date)),
         year = as.factor(year(date))) %>%
  mutate(pm_lv = cut(avg_pm, 
         breaks = c(0,12,35.4,55.4,150.4,250.4,350.4),
         labels = c('good','moderate','unheathy for sensitive groups'
                    ,'unheathy','very unheathy','hazardous'))) %>%
  ggplot(aes(day, y=month, fill=pm_lv)) + 
  geom_tile(color="white") +
  facet_grid(year~.) + ## separate by year
  scale_fill_manual(values = c('green','yellow','orange','red','magenta', 'violet'))+
  coord_equal()

reverse month -> make less value on top

df %>% 
  filter(year(local_dt) > 2015) %>%
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = fct_rev(as_factor(month(date))),  ## remove as.factor and add fct_rev() to month and make it factor using as_factor
         year = as.factor(year(date))) %>%
  mutate(pm_lv = cut(avg_pm, 
         breaks = c(0,12,35.4,55.4,150.4,250.4,350.4),
         labels = c('good','moderate','unheathy for sensitive groups'
                    ,'unheathy','very unheathy','hazardous'))) %>%
  ggplot(aes(day, y=month, fill=pm_lv)) + 
  xlab("Day") + ylab("Month") +
  geom_tile(color="white",width = 2, height=2) +
  facet_grid(year~.) + 
  scale_fill_manual(values = c('green','yellow','orange','red','magenta', 'violet'))+
  coord_equal()

add tooltip

tt <- df %>%  ## put ggplot in to tt (tooltip)
  filter(year(local_dt) > 2017) %>%
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = fct_rev(as_factor(month(date))),  
         year = as.factor(year(date))) %>%
  mutate(pm_lv = cut(avg_pm, 
         breaks = c(0,12,35.4,55.4,150.4,250.4,350.4),
         labels = c('good','moderate','unheathy for sensitive groups'
                    ,'unheathy','very unheathy','hazardous'))) %>%
  ggplot(aes(day, y=month, fill=pm_lv)) + 
  xlab("Day") + ylab("Month") +
  geom_tile(color="white") +
  facet_grid(year~.) + 
  scale_fill_manual(values = c('green','yellow','orange','red','magenta', 'violet'))+
  coord_equal()

ggplotly(tt) # display ggplot with tooltip

figure out scale error by increase number of column and set y’s scale to free

tt<-df %>% 
  filter(Year > 2018) %>%
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = fct_rev(as_factor(month(date))),
         year = as.factor(year(date)),
         pm_lv = cut(avg_pm, 
           breaks = c(0,12,35.4,55.4,150.4,250.4,350.4),
           labels = c('good','moderate','unheathy for sensitive groups'
                      ,'unheathy','very unheathy','hazardous'))
         ) %>%
  ggplot(aes(day, y=month, fill=pm_lv)) + 
  xlab("Day") + ylab("Month") +
  geom_tile(color="white") +
  facet_wrap(year~., scales = "free", ncol=1) + 
  scale_fill_manual(values = c('green','yellow','orange','red','magenta', 'violet'))

  ggplotly(tt)

like above but force square coord_equal()

tt<-df %>% 
  filter(Year > 2018) %>%
  mutate(date = date(local_dt)) %>%
  group_by(date) %>%
  summarize(avg_pm = mean(PM2_5)) %>%
  mutate(day = as.factor(day(date)),
         month = fct_rev(as_factor(month(date))),
         year = as.factor(year(date)),
         pm_lv = cut(avg_pm, 
           breaks = c(0,12,35.4,55.4,150.4,250.4,350.4),
           labels = c('good','moderate','unheathy for sensitive groups'
                      ,'unheathy','very unheathy','hazardous'))
         ) %>%
  ggplot(aes(day, y=month, fill=pm_lv)) + 
  xlab("Day") + ylab("Month") +
  geom_tile(color="white") +
  facet_wrap(year~., scales = "free", ncol=1) + 
  scale_fill_manual(values = c('green','yellow','orange','red','magenta', 'violet'))+
  coord_equal()

  ggplotly(tt)
LS0tDQp0aXRsZTogIlBNIDIuNSBpbiBHZXJtYW55Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGdyaWQpICNmb3IgYWRqdXN0IGdyaWQgc2NhbGUNCmBgYA0KDQpgYGB7cn0NCiNpbXBvcnQgZGF0YSBmcm9tIHVybCBvciBmaWxlDQoNCiMgQ2hpYW5nIE1haSAtIGh0dHA6Ly9iZXJrZWxleWVhcnRoLmxibC5nb3YvYWlyLXF1YWxpdHkvbWFwcy9jaXRpZXMvVGhhaWxhbmQvQ2hpYW5nX01haS9DaGlhbmdfTWFpLnR4dA0KIyBCYW5na29rIC0gaHR0cDovL2JlcmtlbGV5ZWFydGgubGJsLmdvdi9haXItcXVhbGl0eS9tYXBzL2NpdGllcy9UaGFpbGFuZC9CYW5na29rL0Jhbmdrb2sudHh0DQoNCnVybCA8LSAnaHR0cDovL2JlcmtlbGV5ZWFydGgubGJsLmdvdi9haXItcXVhbGl0eS9tYXBzL2NpdGllcy9UaGFpbGFuZC9DaGlhbmdfTWFpL0NoaWFuZ19NYWkudHh0Jw0KZGYgPC0gcmVhZF90c3YodXJsLHNraXA9MTAsIGNvbF9uYW1lcyA9IEZBTFNFKQ0KZGYNCmBgYA0KDQpgYGB7cn0NCmNvbG5hbWVzKGRmKSA8LSBjKCdZZWFyJywnTW9udGgnLCdEYXknLCdIb3VyJywnUE0yXzUnLCdYNicsJ1g3JykNCmRmDQpgYGANCg0KYGBge3J9DQpkZiA8LSBkZiAlPiUNCiAgc2VsZWN0KFllYXI6UE0yXzUpDQpkZg0KYGBgDQoNCmBgYHtyfQ0KZGYgPC0gZGYgJT4lIA0KICBtdXRhdGUoZGF0ZV90aW1lID0gSVNPZGF0ZShZZWFyLE1vbnRoLERheSxIb3VyKSwNCiAgICAgICAgIGxvY2FsX2R0ID0gZGF0ZV90aW1lK2hvdXJzKDcpLA0KICAgICAgICAgbG9jYWxfaHIgPSBob3VyKGxvY2FsX2R0KSkNCmRmDQpgYGANCiAtLS0tLS0gU3VtbWFyaXplZCBEYXRhIChSZXNlcnZlZCkgLS0tLS0tDQoNCmBgYHtyfQ0KZGYgJT4lIA0KICBtdXRhdGUoeWVhciA9IHllYXIobG9jYWxfZHQpLA0KICAgICAgICAgbW9udGggPSBtb250aChsb2NhbF9kdCksDQogICAgICAgICBkYXRlID0gZGF0ZShsb2NhbF9kdCkpICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKGF2Z19wbSA9IG1lYW4oUE0yXzUpKQ0KYGBgDQogLS0tLS0tIFZpc3VhbGl6ZWQgRGF0YSAtLS0tLS0NCiANCiAjIyBlbnRpcmUgZGF0YQ0KYGBge3J9DQpkZiAlPiUgDQogIG11dGF0ZSh5ZWFyID0geWVhcihsb2NhbF9kdCksDQogICAgICAgICBtb250aCA9IG1vbnRoKGxvY2FsX2R0KSwNCiAgICAgICAgIGRhdGUgPSBkYXRlKGxvY2FsX2R0KSkgJT4lDQogIGdyb3VwX2J5KGRhdGUpICU+JQ0KICBzdW1tYXJpemUoYXZnX3BtID0gbWVhbihQTTJfNSkpICU+JQ0KICBnZ3Bsb3QoYWVzKGRhdGUsIHkgPSBhdmdfcG0gLCBjb2xvciA9IGFzLmZhY3Rvcih5ZWFyKGRhdGUpKSkpK2dlb21fbGluZSgpK2dlb21fc21vb3RoKCkNCmBgYA0KDQojIyBieSBkYXRlIGZvciBlYWNoIG1vbnRoDQpgYGB7cn0NCmRmICU+JSANCiAgbXV0YXRlKHllYXIgPSB5ZWFyKGxvY2FsX2R0KSwNCiAgICAgICAgIG1vbnRoID0gbW9udGgobG9jYWxfZHQpLA0KICAgICAgICAgZGF0ZSA9IGRhdGUobG9jYWxfZHQpKSAlPiUNCiAgZ3JvdXBfYnkoeWVhcixtb250aCxkYXRlKSAlPiUNCiAgc3VtbWFyaXplKGF2Z19wbSA9IG1lYW4oUE0yXzUpKSAlPiUNCiAgZmlsdGVyKHllYXIgPT0gMjAxOSkgJT4lDQogIGdncGxvdChhZXMoZGF0ZSwgeSA9IGF2Z19wbSAsIGNvbG9yID0gYXMuZmFjdG9yKG1vbnRoKSkpK2dlb21fbGluZSgpDQpgYGANCg0KIyMgQnkgbW9udGggZm9yIGVhY2ggeWVhcg0KYGBge3J9DQpkZiAlPiUgDQogIG11dGF0ZSh5ZWFyID0geWVhcihsb2NhbF9kdCksDQogICAgICAgICBtb250aCA9IG1vbnRoKGxvY2FsX2R0KSwNCiAgICAgICAgIGRhdGUgPSBkYXRlKGxvY2FsX2R0KSkgJT4lDQogIGdyb3VwX2J5KHllYXIsbW9udGgpICU+JQ0KICBzdW1tYXJpemUoYXZnX3BtID0gbWVhbihQTTJfNSkpICU+JQ0KICBnZ3Bsb3QoYWVzKG1vbnRoLCB5ID0gYXZnX3BtICwgY29sb3IgPSBhcy5mYWN0b3IoeWVhcikpKSsgZ2VvbV9zbW9vdGgoc2U9RkFMU0UpICtzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9bW9udGguYWJiKSAgDQpgYGANCiANCiAjIyBjcmVhdGUgaGVhdCBtYXANCmBgYHtyfQ0KZGYgJT4lIA0KICBtdXRhdGUoZGF0ZSA9IGRhdGUobG9jYWxfZHQpKSAlPiUNCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lDQogIHN1bW1hcml6ZShhdmdfcG0gPSBtZWFuKFBNMl81KSkgJT4lDQogIG11dGF0ZShkYXkgPSBkYXkoZGF0ZSksDQogICAgICAgICBtb250aCA9IG1vbnRoKGRhdGUpLA0KICAgICAgICAgeWVhciA9IHllYXIoZGF0ZSkpICU+JQ0KICBnZ3Bsb3QoYWVzKGRheSwgeT1tb250aCwgZmlsbD1hdmdfcG0pKSArIGdlb21fdGlsZSgpDQpgYGANCiANCiAjIyBhZGQgZmFjdG9yIHRvIG1hcCwgbWFrZSBpdCBkaXNwbGF5IGJ5IGRheQ0KIA0KYGBge3J9DQpkZiAlPiUgDQogIG11dGF0ZShkYXRlID0gZGF0ZShsb2NhbF9kdCkpICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKGF2Z19wbSA9IG1lYW4oUE0yXzUpKSAlPiUNCiAgbXV0YXRlKGRheSA9IGFzLmZhY3RvcihkYXkoZGF0ZSkpLA0KICAgICAgICAgbW9udGggPSBhcy5mYWN0b3IobW9udGgoZGF0ZSkpLA0KICAgICAgICAgeWVhciA9IGFzLmZhY3Rvcih5ZWFyKGRhdGUpKSkgJT4lDQogIGdncGxvdChhZXMoZGF5LCB5PW1vbnRoLCBmaWxsPWF2Z19wbSkpICsgZ2VvbV90aWxlKCkNCmBgYA0KIA0KICMjIGFkZCBib3JkZXIgYW5kIHNldCBhIGNvbG9yDQpgYGB7cn0NCmRmICU+JSANCiAgbXV0YXRlKGRhdGUgPSBkYXRlKGxvY2FsX2R0KSkgJT4lDQogIGdyb3VwX2J5KGRhdGUpICU+JQ0KICBzdW1tYXJpemUoYXZnX3BtID0gbWVhbihQTTJfNSkpICU+JQ0KICBtdXRhdGUoZGF5ID0gYXMuZmFjdG9yKGRheShkYXRlKSksDQogICAgICAgICBtb250aCA9IGFzLmZhY3Rvcihtb250aChkYXRlKSksDQogICAgICAgICB5ZWFyID0gYXMuZmFjdG9yKHllYXIoZGF0ZSkpKSAlPiUNCiAgZ2dwbG90KGFlcyhkYXksIHk9bW9udGgsIGZpbGw9YXZnX3BtKSkgKw0KICBnZW9tX3RpbGUoY29sb3I9IndoaXRlIikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJncmVlbiIgLCBoaWdoID0gJ3JlZCcpDQpgYGANCiANCiMjIGFkanVzdCBjZWxsIHNjYWxlDQpgYGB7ciwgZmlnLmhpZ2h0PTIsIGZpZy53aWR0aD04fSANCmRmICU+JSANCiAgbXV0YXRlKGRhdGUgPSBkYXRlKGxvY2FsX2R0KSkgJT4lDQogIGdyb3VwX2J5KGRhdGUpICU+JQ0KICBzdW1tYXJpemUoYXZnX3BtID0gbWVhbihQTTJfNSkpICU+JQ0KICBtdXRhdGUoZGF5ID0gYXMuZmFjdG9yKGRheShkYXRlKSksDQogICAgICAgICBtb250aCA9IGFzLmZhY3Rvcihtb250aChkYXRlKSksDQogICAgICAgICB5ZWFyID0gYXMuZmFjdG9yKHllYXIoZGF0ZSkpKSAlPiUNCiAgZ2dwbG90KGFlcyhkYXksIHk9bW9udGgsIGZpbGw9YXZnX3BtKSkgKw0KICBnZW9tX3RpbGUoY29sb3I9IndoaXRlIikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJncmVlbiIgLCBoaWdoID0gJ3JlZCcpICsNCiAgY29vcmRfZXF1YWwoKQ0KYGBgDQojIyBzZXQgaW50ZXJ2YWwgZmFjdG9yIGZvciBhdmcgcG0gMi41DQoNCmBgYHtyfQ0KZGYgJT4lIA0KICBtdXRhdGUoZGF0ZSA9IGRhdGUobG9jYWxfZHQpKSAlPiUNCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lDQogIHN1bW1hcml6ZShhdmdfcG0gPSBtZWFuKFBNMl81KSkgJT4lDQogIG11dGF0ZShkYXkgPSBhcy5mYWN0b3IoZGF5KGRhdGUpKSwNCiAgICAgICAgIG1vbnRoID0gYXMuZmFjdG9yKG1vbnRoKGRhdGUpKSwNCiAgICAgICAgIHllYXIgPSBhcy5mYWN0b3IoeWVhcihkYXRlKSkpICU+JQ0KICBtdXRhdGUocG1fbHYgPSBjdXQoYXZnX3BtLCAjIyBjcmVhdGUgbmV3IGNvbHVtbiB0byBjbGFzc2lmeSBsZXZlbCBvZiBzbW9nDQogICAgICAgICBicmVha3MgPSBjKDAsMTIsMzUuNCw1NS40LDE1MC40LDI1MC40LDM1MC40KSwNCiAgICAgICAgIGxhYmVscyA9IGMoJ2dvb2QnLCdtb2RlcmF0ZScsJ3VuaGVhdGh5IGZvciBzZW5zaXRpdmUgZ3JvdXBzJw0KICAgICAgICAgICAgICAgICAgICAsJ3VuaGVhdGh5JywndmVyeSB1bmhlYXRoeScsJ2hhemFyZG91cycpKSkgJT4lDQogIGdncGxvdChhZXMoZGF5LCB5PW1vbnRoLCBmaWxsPXBtX2x2KSkgKyAjIyByZXBsYWNlIGF2Z19wbSB3aXRoIHBtX2xldmVsDQogIGdlb21fdGlsZShjb2xvcj0id2hpdGUiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJ2dyZWVuJywneWVsbG93Jywnb3JhbmdlJywncmVkJywnbWFnZW50YScsICd2aW9sZXQnKSkrDQogIGNvb3JkX2VxdWFsKCkNCmBgYA0KDQojIyBzZXBhcmF0ZSBieSB5ZWFyDQpgYGB7cixmaWcuaGlnaHQ9MTIsIGZpZy53aWR0aD0xNn0NCmRmICU+JSANCiAgZmlsdGVyKHllYXIobG9jYWxfZHQpID4gMjAxNSkgJT4lDQogIG11dGF0ZShkYXRlID0gZGF0ZShsb2NhbF9kdCkpICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKGF2Z19wbSA9IG1lYW4oUE0yXzUpKSAlPiUNCiAgbXV0YXRlKGRheSA9IGFzLmZhY3RvcihkYXkoZGF0ZSkpLA0KICAgICAgICAgbW9udGggPSBhcy5mYWN0b3IobW9udGgoZGF0ZSkpLA0KICAgICAgICAgeWVhciA9IGFzLmZhY3Rvcih5ZWFyKGRhdGUpKSkgJT4lDQogIG11dGF0ZShwbV9sdiA9IGN1dChhdmdfcG0sIA0KICAgICAgICAgYnJlYWtzID0gYygwLDEyLDM1LjQsNTUuNCwxNTAuNCwyNTAuNCwzNTAuNCksDQogICAgICAgICBsYWJlbHMgPSBjKCdnb29kJywnbW9kZXJhdGUnLCd1bmhlYXRoeSBmb3Igc2Vuc2l0aXZlIGdyb3VwcycNCiAgICAgICAgICAgICAgICAgICAgLCd1bmhlYXRoeScsJ3ZlcnkgdW5oZWF0aHknLCdoYXphcmRvdXMnKSkpICU+JQ0KICBnZ3Bsb3QoYWVzKGRheSwgeT1tb250aCwgZmlsbD1wbV9sdikpICsgDQogIGdlb21fdGlsZShjb2xvcj0id2hpdGUiKSArDQogIGZhY2V0X2dyaWQoeWVhcn4uKSArICMjIHNlcGFyYXRlIGJ5IHllYXINCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygnZ3JlZW4nLCd5ZWxsb3cnLCdvcmFuZ2UnLCdyZWQnLCdtYWdlbnRhJywgJ3Zpb2xldCcpKSsNCiAgY29vcmRfZXF1YWwoKQ0KDQpgYGANCiMjIHJldmVyc2UgbW9udGggLT4gbWFrZSBsZXNzIHZhbHVlIG9uIHRvcA0KYGBge3IsZmlnLmhpZ2h0PTEyLCBmaWcud2lkdGg9MTZ9DQpkZiAlPiUgDQogIGZpbHRlcih5ZWFyKGxvY2FsX2R0KSA+IDIwMTUpICU+JQ0KICBtdXRhdGUoZGF0ZSA9IGRhdGUobG9jYWxfZHQpKSAlPiUNCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lDQogIHN1bW1hcml6ZShhdmdfcG0gPSBtZWFuKFBNMl81KSkgJT4lDQogIG11dGF0ZShkYXkgPSBhcy5mYWN0b3IoZGF5KGRhdGUpKSwNCiAgICAgICAgIG1vbnRoID0gZmN0X3Jldihhc19mYWN0b3IobW9udGgoZGF0ZSkpKSwgICMjIHJlbW92ZSBhcy5mYWN0b3IgYW5kIGFkZCBmY3RfcmV2KCkgdG8gbW9udGggYW5kIG1ha2UgaXQgZmFjdG9yIHVzaW5nIGFzX2ZhY3Rvcg0KICAgICAgICAgeWVhciA9IGFzLmZhY3Rvcih5ZWFyKGRhdGUpKSkgJT4lDQogIG11dGF0ZShwbV9sdiA9IGN1dChhdmdfcG0sIA0KICAgICAgICAgYnJlYWtzID0gYygwLDEyLDM1LjQsNTUuNCwxNTAuNCwyNTAuNCwzNTAuNCksDQogICAgICAgICBsYWJlbHMgPSBjKCdnb29kJywnbW9kZXJhdGUnLCd1bmhlYXRoeSBmb3Igc2Vuc2l0aXZlIGdyb3VwcycNCiAgICAgICAgICAgICAgICAgICAgLCd1bmhlYXRoeScsJ3ZlcnkgdW5oZWF0aHknLCdoYXphcmRvdXMnKSkpICU+JQ0KICBnZ3Bsb3QoYWVzKGRheSwgeT1tb250aCwgZmlsbD1wbV9sdikpICsgDQogIHhsYWIoIkRheSIpICsgeWxhYigiTW9udGgiKSArDQogIGdlb21fdGlsZShjb2xvcj0id2hpdGUiKSArDQogIGZhY2V0X2dyaWQoeWVhcn4uKSArIA0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCdncmVlbicsJ3llbGxvdycsJ29yYW5nZScsJ3JlZCcsJ21hZ2VudGEnLCAndmlvbGV0JykpKw0KICBjb29yZF9lcXVhbCgpDQpgYGANCiMjIGFkZCB0b29sdGlwDQoNCmBgYHtyfQ0KdHQgPC0gZGYgJT4lICAjIyBwdXQgZ2dwbG90IGluIHRvIHR0ICh0b29sdGlwKQ0KICBmaWx0ZXIoeWVhcihsb2NhbF9kdCkgPiAyMDE3KSAlPiUNCiAgbXV0YXRlKGRhdGUgPSBkYXRlKGxvY2FsX2R0KSkgJT4lDQogIGdyb3VwX2J5KGRhdGUpICU+JQ0KICBzdW1tYXJpemUoYXZnX3BtID0gbWVhbihQTTJfNSkpICU+JQ0KICBtdXRhdGUoZGF5ID0gYXMuZmFjdG9yKGRheShkYXRlKSksDQogICAgICAgICBtb250aCA9IGZjdF9yZXYoYXNfZmFjdG9yKG1vbnRoKGRhdGUpKSksICANCiAgICAgICAgIHllYXIgPSBhcy5mYWN0b3IoeWVhcihkYXRlKSkpICU+JQ0KICBtdXRhdGUocG1fbHYgPSBjdXQoYXZnX3BtLCANCiAgICAgICAgIGJyZWFrcyA9IGMoMCwxMiwzNS40LDU1LjQsMTUwLjQsMjUwLjQsMzUwLjQpLA0KICAgICAgICAgbGFiZWxzID0gYygnZ29vZCcsJ21vZGVyYXRlJywndW5oZWF0aHkgZm9yIHNlbnNpdGl2ZSBncm91cHMnDQogICAgICAgICAgICAgICAgICAgICwndW5oZWF0aHknLCd2ZXJ5IHVuaGVhdGh5JywnaGF6YXJkb3VzJykpKSAlPiUNCiAgZ2dwbG90KGFlcyhkYXksIHk9bW9udGgsIGZpbGw9cG1fbHYpKSArIA0KICB4bGFiKCJEYXkiKSArIHlsYWIoIk1vbnRoIikgKw0KICBnZW9tX3RpbGUoY29sb3I9IndoaXRlIikgKw0KICBmYWNldF9ncmlkKHllYXJ+LikgKyANCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygnZ3JlZW4nLCd5ZWxsb3cnLCdvcmFuZ2UnLCdyZWQnLCdtYWdlbnRhJywgJ3Zpb2xldCcpKSsNCiAgY29vcmRfZXF1YWwoKQ0KDQpnZ3Bsb3RseSh0dCkgIyBkaXNwbGF5IGdncGxvdCB3aXRoIHRvb2x0aXANCmBgYA0KDQoNCiMjIGZpZ3VyZSBvdXQgc2NhbGUgZXJyb3IgYnkgaW5jcmVhc2UgbnVtYmVyIG9mIGNvbHVtbiBhbmQgc2V0IHkncyBzY2FsZSB0byBmcmVlDQpgYGB7cn0NCnR0PC1kZiAlPiUgDQogIGZpbHRlcihZZWFyID4gMjAxNSkgJT4lDQogIG11dGF0ZShkYXRlID0gZGF0ZShsb2NhbF9kdCkpICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKGF2Z19wbSA9IG1lYW4oUE0yXzUpKSAlPiUNCiAgbXV0YXRlKGRheSA9IGFzLmZhY3RvcihkYXkoZGF0ZSkpLA0KICAgICAgICAgbW9udGggPSBmY3RfcmV2KGFzX2ZhY3Rvcihtb250aChkYXRlKSkpLA0KICAgICAgICAgeWVhciA9IGFzLmZhY3Rvcih5ZWFyKGRhdGUpKSwNCiAgICAgICAgIHBtX2x2ID0gY3V0KGF2Z19wbSwgDQogICAgICAgICAgIGJyZWFrcyA9IGMoMCwxMiwzNS40LDU1LjQsMTUwLjQsMjUwLjQsMzUwLjQpLA0KICAgICAgICAgICBsYWJlbHMgPSBjKCdnb29kJywnbW9kZXJhdGUnLCd1bmhlYXRoeSBmb3Igc2Vuc2l0aXZlIGdyb3VwcycNCiAgICAgICAgICAgICAgICAgICAgICAsJ3VuaGVhdGh5JywndmVyeSB1bmhlYXRoeScsJ2hhemFyZG91cycpKQ0KICAgICAgICAgKSAlPiUNCiAgZ2dwbG90KGFlcyhkYXksIHk9bW9udGgsIGZpbGw9cG1fbHYpKSArIA0KICB4bGFiKCJEYXkiKSArIHlsYWIoIk1vbnRoIikgKw0KICBnZW9tX3RpbGUoY29sb3I9IndoaXRlIikgKw0KICBmYWNldF93cmFwKHllYXJ+Liwgc2NhbGVzID0gImZyZWUiLCBuY29sPTIpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJ2dyZWVuJywneWVsbG93Jywnb3JhbmdlJywncmVkJywnbWFnZW50YScsICd2aW9sZXQnKSkNCg0KICBnZ3Bsb3RseSh0dCkNCmBgYA0KDQojIGxpa2UgYWJvdmUgYnV0IGZvcmNlIHNxdWFyZSBjb29yZF9lcXVhbCgpDQpgYGB7cn0NCnR0PC1kZiAlPiUgDQogIGZpbHRlcihZZWFyID4gMjAxOCkgJT4lDQogIG11dGF0ZShkYXRlID0gZGF0ZShsb2NhbF9kdCkpICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKGF2Z19wbSA9IG1lYW4oUE0yXzUpKSAlPiUNCiAgbXV0YXRlKGRheSA9IGFzLmZhY3RvcihkYXkoZGF0ZSkpLA0KICAgICAgICAgbW9udGggPSBmY3RfcmV2KGFzX2ZhY3Rvcihtb250aChkYXRlKSkpLA0KICAgICAgICAgeWVhciA9IGFzLmZhY3Rvcih5ZWFyKGRhdGUpKSwNCiAgICAgICAgIHBtX2x2ID0gY3V0KGF2Z19wbSwgDQogICAgICAgICAgIGJyZWFrcyA9IGMoMCwxMiwzNS40LDU1LjQsMTUwLjQsMjUwLjQsMzUwLjQpLA0KICAgICAgICAgICBsYWJlbHMgPSBjKCdnb29kJywnbW9kZXJhdGUnLCd1bmhlYXRoeSBmb3Igc2Vuc2l0aXZlIGdyb3VwcycNCiAgICAgICAgICAgICAgICAgICAgICAsJ3VuaGVhdGh5JywndmVyeSB1bmhlYXRoeScsJ2hhemFyZG91cycpKQ0KICAgICAgICAgKSAlPiUNCiAgZ2dwbG90KGFlcyhkYXksIHk9bW9udGgsIGZpbGw9cG1fbHYpKSArIA0KICB4bGFiKCJEYXkiKSArIHlsYWIoIk1vbnRoIikgKw0KICBnZW9tX3RpbGUoY29sb3I9IndoaXRlIikgKw0KICBmYWNldF93cmFwKHllYXJ+Liwgc2NhbGVzID0gImZyZWUiLCBuY29sPTEpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJ2dyZWVuJywneWVsbG93Jywnb3JhbmdlJywncmVkJywnbWFnZW50YScsICd2aW9sZXQnKSkrDQogIGNvb3JkX2VxdWFsKCkNCg0KICBnZ3Bsb3RseSh0dCkNCmBgYA==